﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Xml.Linq;

using ApocDev.TownsApi.Aspects;

namespace ApocDev.TownsApi.XmlFiles
{
    [NotifyPropertyChanged]
    public class EffectsXml : ObservableCollection<Effect>, IChangedXml
    {
        // Unfortunately, this is one of those files where things have to be "specially" handled to allow
        // people to create their own things.
        public EffectsXml(string actionsPath)
        {
            // Need to set this, bit hackish, but whatever!
            ModFiles.Effects = this;

            XElement root = XElement.Load(actionsPath);
            IEnumerable<XElement> elements = root.Elements();
            int total = elements.Count();
            ModFiles.FireLoadEvent(this, new FileLoadEventArgs("Effects", total, 0));
            int numLoaded = 0;
            foreach (XElement xml in elements)
            {
                Add(new Effect(xml));
                ModFiles.FireLoadEvent(this, new FileLoadEventArgs("Effects", total, ++numLoaded));
            }

            // This is needed since the XML isn't loaded in a way that has "onHitEffects" loaded before ones using them.
            foreach (Effect e in this)
            {
                e.RebuildEffectsParenting();
            }
        }

        public bool IsDirty { get { return this.Any(a => a.IsDirty); } }

        public XElement SaveDirty()
        {
            if (!IsDirty)
                return null;
            var actions = new XElement("effects");
            foreach (Effect a in this)
            {
                if (a.IsDirty)
                    actions.Add(a.Save());
            }
            return actions;
        }

        public XElement Save()
        {
            var actions = new XElement("effects");
            foreach (Effect a in this)
            {
                actions.Add(a.Save());
            }
            return actions;
        }
    }
    [NotifyPropertyChanged]
    public class Effect : IEquatable<Effect>, IChangedXml
    {
        private readonly int _cleanHash;
        private readonly string _loadedAfterEffects = string.Empty;
        private readonly string _loadedCastEffects = string.Empty;
        private readonly string _loadedEffectsImmune = string.Empty;
        private readonly string _loadedOnHitEffects = string.Empty;
        private readonly string _loadedOnRangedHitEffects = string.Empty;

        public Effect(XElement xml)
        {
            MessageWhenVanish = true;
            OnRangedHitEffects = new ObservableCollection<Effect>();
            OnHitEffects = new ObservableCollection<Effect>();
            AfterEffects = new ObservableCollection<Effect>();
            CastEffects = new ObservableCollection<Effect>();
            EffectsImmune = new ObservableCollection<Effect>();

            EffectsImmune.GetHashCode();
            foreach (XElement e in xml.Elements())
            {
                switch (e.Name.LocalName.ToLower())
                {
                    case "id":
                        ID = e.Value;
                        break;
                    case "name":
                        Name = e.Value;
                        break;
                    case "icon":
                        Icon = e.Value;
                        break;
                    case "damagepct":
                        DamagePCT = int.Parse(e.Value);
                        break;
                    case "attackpct":
                        AttackPCT = int.Parse(e.Value);
                        break;
                    case "speedpct":
                        SpeedPCT = int.Parse(e.Value);
                        break;
                    case "attackspeedpct":
                        AttackSpeedPCT = int.Parse(e.Value);
                        break;
                    case "healthpointspct":
                        HealthPointsPCT = int.Parse(e.Value);
                        break;
                    case "defensepct":
                        DefensePCT = int.Parse(e.Value);
                        break;
                    case "graphicchange":
                        GraphicChange = e.Value;
                        break;
                    case "lasts":
                        Lasts = new RandomRoll(e.Value);
                        break;
                    case "dot":
                        DOT = new RandomRoll(e.Value);
                        break;
                    case "castcooldown":
                        CastCooldown = new RandomRoll(e.Value);
                        break;
                    case "messagewhenvanish":
                        // Only happens when we're told *not* to show it.
                        MessageWhenVanish = e.Value.ToLower() != "false";
                        break;
                    case "onrangedhitpct":
                        OnRangedHitPCT = int.Parse(e.Value);
                        break;
                    case "onhitpct":
                        OnHitPCT = int.Parse(e.Value);
                        break;
                    case "casttargets":
                        CastTargets = e.Value;
                        break;
                    case "onrangedhiteffects":
                        _loadedOnRangedHitEffects = e.Value;
                        break;
                    case "onhiteffects":
                        _loadedOnHitEffects = e.Value;
                        break;
                    case "aftereffects":
                        _loadedAfterEffects = e.Value;
                        break;
                    case "effectsimmune":
                        _loadedEffectsImmune = e.Value;
                        break;
                    case "casteffects":
                        _loadedCastEffects = e.Value;
                        break;
                    case "removetarget":
                        RemoveTarget = e.Value.ToLower() != "false";
                        break;
                    case "attackallies":
                        AttackAllies = e.Value.ToLower() != "false";
                        break;
                        
                    default:
                        MessageBox.Show("Unknown element for " + GetType().Name + ": " + e.Name.LocalName + " -> " + e.Value);
                        break;
                }
            }
            _cleanHash = GetHashCode();
        }

        public string ID { get; set; }
        public string Name { get; set; }
        public string Icon { get; set; }
        public int DamagePCT { get; set; }
        public int AttackPCT { get; set; }
        public int SpeedPCT { get; set; }
        public int AttackSpeedPCT { get; set; }
        public int HealthPointsPCT { get; set; }
        public int DefensePCT { get; set; }

        public string GraphicChange { get; set; }
        public RandomRoll Lasts { get; set; }
        public RandomRoll DOT { get; set; }
        public RandomRoll CastCooldown { get; set; }
        public bool MessageWhenVanish { get; set; }

        public int OnRangedHitPCT { get; set; }
        public int OnHitPCT { get; set; }
        public string CastTargets { get; set; }
        public bool RemoveTarget { get; set; }
        public bool AttackAllies { get; set; }
        public ObservableCollection<Effect> OnRangedHitEffects { get; set; }

        public ObservableCollection<Effect> OnHitEffects { get; set; }
        public ObservableCollection<Effect> AfterEffects { get; set; }
        public ObservableCollection<Effect> CastEffects { get; set; }
        public ObservableCollection<Effect> EffectsImmune { get; set; }

        #region Equality

        public bool Equals(Effect other)
        {
            if (ReferenceEquals(null, other))
                return false;
            if (ReferenceEquals(this, other))
                return true;
            return string.Equals(ID, other.ID) && string.Equals(Name, other.Name) && string.Equals(Icon, other.Icon) &&
                   DamagePCT == other.DamagePCT && SpeedPCT == other.SpeedPCT && AttackSpeedPCT == other.AttackSpeedPCT &&
                   HealthPointsPCT == other.HealthPointsPCT && DefensePCT == other.DefensePCT &&
                   string.Equals(GraphicChange, other.GraphicChange) && Equals(Lasts, other.Lasts) &&
                   Equals(DOT, other.DOT) && Equals(CastCooldown, other.CastCooldown) &&
                   MessageWhenVanish.Equals(other.MessageWhenVanish) && OnRangedHitPCT == other.OnRangedHitPCT &&
                   OnHitPCT == other.OnHitPCT && string.Equals(CastTargets, other.CastTargets) &&
                   Equals(OnRangedHitEffects, other.OnRangedHitEffects) && Equals(OnHitEffects, other.OnHitEffects) &&
                   Equals(AfterEffects, other.AfterEffects) && Equals(CastEffects, other.CastEffects) &&
                   Equals(EffectsImmune, other.EffectsImmune);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != GetType())
                return false;
            return Equals((Effect) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = (ID != null ? ID.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Icon != null ? Icon.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ DamagePCT;
                hashCode = (hashCode * 397) ^ SpeedPCT;
                hashCode = (hashCode * 397) ^ AttackSpeedPCT;
                hashCode = (hashCode * 397) ^ HealthPointsPCT;
                hashCode = (hashCode * 397) ^ DefensePCT;
                hashCode = (hashCode * 397) ^ (GraphicChange != null ? GraphicChange.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Lasts != null ? Lasts.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (DOT != null ? DOT.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (CastCooldown != null ? CastCooldown.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ MessageWhenVanish.GetHashCode();
                hashCode = (hashCode * 397) ^ OnRangedHitPCT;
                hashCode = (hashCode * 397) ^ OnHitPCT;
                hashCode = (hashCode * 397) ^ (CastTargets != null ? CastTargets.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (OnRangedHitEffects != null ? OnRangedHitEffects.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (OnHitEffects != null ? OnHitEffects.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (AfterEffects != null ? AfterEffects.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (CastEffects != null ? CastEffects.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (EffectsImmune != null ? EffectsImmune.GetHashCode() : 0);
                return hashCode;
            }
        }

        public static bool operator ==(Effect left, Effect right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Effect left, Effect right)
        {
            return !Equals(left, right);
        }

        #endregion

        internal void RebuildEffectsParenting()
        {
            IEnumerable<string> split =
                _loadedOnHitEffects.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
            foreach (string s in split)
            {
                OnHitEffects.Add(ModFiles.GetEffect(s));
            }
            split =
                _loadedOnRangedHitEffects.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
                                         .Select(s => s.Trim());
            foreach (string s in split)
            {
                OnRangedHitEffects.Add(ModFiles.GetEffect(s));
            }
            split = _loadedAfterEffects.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
            foreach (string s in split)
            {
                AfterEffects.Add(ModFiles.GetEffect(s));
            }
            split = _loadedEffectsImmune.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
            foreach (string s in split)
            {
                EffectsImmune.Add(ModFiles.GetEffect(s));
            }
            split = _loadedCastEffects.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
            foreach (string s in split)
            {
                CastEffects.Add(ModFiles.GetEffect(s));
            }
        }

        public bool IsDirty { get { return _cleanHash != GetHashCode(); } }
        public XElement Save()
        {
            return new XElement("effect",
                new XElement("id", ID),
                new XElement("name", Name),
                Xml.AddIf("icon", Icon),
                Xml.AddIf("damagePCT", DamagePCT),
                Xml.AddIf("attackPCT", AttackPCT),
                Xml.AddIf("speedPCT", SpeedPCT),
                Xml.AddIf("attackSpeedPCT", AttackSpeedPCT),
                Xml.AddIf("healthPointsPCT", HealthPointsPCT),
                Xml.AddIf("defensePCT", DefensePCT),
                Xml.AddIf("graphicsChange", GraphicChange),
                Xml.AddIf("lasts", Lasts),
                Xml.AddIf("DOT", DOT),
                Xml.AddIf("castCooldown", CastCooldown),
                Xml.AddIf("messageWhenVanish", MessageWhenVanish, true),
                Xml.AddIf("onRangedHitPCT", OnRangedHitPCT),
                Xml.AddIf("onHitPCT", OnHitPCT),
                Xml.AddIf("castTargets", CastTargets),
                Xml.AddIf("onRangedHitEffects", string.Join(",", OnRangedHitEffects)),
                Xml.AddIf("onHitEffects", string.Join(",", OnHitEffects)),
                Xml.AddIf("afterEffects", string.Join(",", AfterEffects)),
                Xml.AddIf("castEffects", string.Join(",", CastEffects)),
                Xml.AddIf("effectsImmune", string.Join(",", EffectsImmune)),
                Xml.AddIf("removeTarget", RemoveTarget),
                Xml.AddIf("attackAllies", AttackAllies)
                );
        }

        public XElement SaveDirty()
        {
            return Save();
        }
    }
}